/*
 * © 2024 huayunliufeng保留所有权利, 依据MIT许可证发布。
 * 请勿更改或删除版权声明或此文件头。
 * 此代码是免费软件, 您可以重新分发和/或修改它。
 * 开源是希望它有用, 但不对代码做任何保证。
 * 如有疑问请联系: huayunliufeng@163.com
 */

package io.github.huayunliufeng.dbinfo.dbinfo;

import io.github.huayunliufeng.common.utils.HylfCollectionUtil;
import io.github.huayunliufeng.common.utils.HylfDataUtil;
import io.github.huayunliufeng.common.utils.HylfFunUtil;
import io.github.huayunliufeng.dbinfo.model.PrivilegeInfo;
import io.github.huayunliufeng.dbinfo.model.SuperTablesInfo;
import io.github.huayunliufeng.dbinfo.model.TableInfo;
import io.github.huayunliufeng.dbinfo.model.VersionColumnsInfo;
import lombok.extern.slf4j.Slf4j;

import java.sql.Connection;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 获取表信息。
 *
 * @author huayunliufeng
 * @date_time 2024/3/23 22:43
 */
@Slf4j
public class ObtainTableInfo extends ObtainColumnInfo {

    public static final String TABLE_TYPE = "TABLE_TYPE";

    private static Map<Connection, ObtainTableInfo> obtainTableInfoMap = null;

    private Map<String, List<TableInfo>> tableInfoMap = null;
    private Map<String, List<PrivilegeInfo>> tablePrivilegesInfoMap = null;
    private Map<String, List<SuperTablesInfo>> superTableInfosMap = null;
    private Map<String, List<VersionColumnsInfo>> versionColumnsInfoMap = null;

    private List<String> typeList = null;

    protected ObtainTableInfo(Connection conn) {
        super(conn);
    }

    public static ObtainTableInfo build(Connection conn) {
        obtainTableInfoMap = HylfFunUtil.mapAddValue(obtainTableInfoMap, conn, () -> new ObtainTableInfo(conn));
        return obtainTableInfoMap.get(conn);
    }

    /**
     * <p>检索给定目录中可用表的描述。只返回与目录、模式、表名和类型条件匹配的表描述。</p>
     * <p>它们按TABLE_TYPE, TABLE_CAT, TABLE_SCHEMA和TABLE_NAME排序。</p>
     *
     * @param catalog          目录名称;必须与存储在数据库中的目录名称匹配;“”检索那些没有目录的;null表示不应使用目录名称来缩小搜索范围
     * @param schemaPattern    模式名称模式;必须与存储在数据库中的模式名称匹配;""检索那些没有模式的;null表示不应该使用模式名称来缩小搜索范围
     * @param tableNamePattern 一个表名模式;必须匹配存储在数据库中的表名
     * @param types            表类型列表，它必须来自从getTableTypes返回的表类型列表，null返回所有类型
     * @return 每一行都是一个表描述
     */
    public List<TableInfo> getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) {
        String logFormat = "获取数据库表信息失败。[catalog = {}, schemaPattern = {}, tableNamePattern = {}, types = {}]";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            String key = String.join("-", catalog, schemaPattern, tableNamePattern);
            tableInfoMap = HylfFunUtil.mapAddValue(tableInfoMap, key, () -> {
                ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(
                        () -> databaseMetaData.getTables(catalog, schemaPattern, tableNamePattern, types)
                        , logFormat, catalog, schemaPattern, tableNamePattern, types);
                List<TableInfo> tableInfoList = HylfDataUtil.autoSetValue(resultSet, TableInfo.class);
                for (TableInfo tableInfo : tableInfoList) {
                    String tableCat = tableInfo.getTableCat();
                    String tableSchem = tableInfo.getTableSchem();
                    String tableName = tableInfo.getTableName();
                    // 获取字段信息
                    tableInfo.setColumnInfoList(getColumns(tableCat, tableSchem, tableName));
                    // 获取主键信息
                    tableInfo.setPrimaryKeyInfoList(ObtainPrimaryKeyInfo.build(conn).getPrimaryKeys(tableCat, tableSchem, tableName));
                    // 获取索引信息
                    tableInfo.setIndexInfoList(ObtainIndexInfo.build(conn).getIndexInfo(tableCat, tableSchem, tableName));
                    // 获取外键信息
                    tableInfo.setExportedKeyInfoList(ObtainExportedKeyInfo.build(conn).getExportedKeys(tableCat, tableSchem, tableName));
                    // 获取可用表的访问权限
                    tableInfo.setPrivilegesInfoList(getTablePrivileges(tableCat, tableSchem, tableName));
                    // 获取数据库的特定模式中定义的表层次结构的描述。
                    tableInfo.setSuperTablesInfoList(getSuperTables(tableCat, tableSchem, tableName));
                    // 获取在更新行中的任何值时自动更新的表列的描述。
                    tableInfo.setVersionColumnsInfoList(getVersionColumns(tableCat, tableSchem, tableName));
                }
                HylfFunUtil.autoClose(resultSet);
                return tableInfoList;
            });
            return tableInfoMap.get(key);
        });
    }

    /**
     * <p>检索给定目录中可用表的描述。只返回与目录、模式、表名和类型条件匹配的表描述。</p>
     * <p>它们按TABLE_TYPE, TABLE_CAT, TABLE_SCHEMA和TABLE_NAME排序。</p>
     *
     * @param schemaPattern    模式名称模式;必须与存储在数据库中的模式名称匹配;""检索那些没有模式的;null表示不应该使用模式名称来缩小搜索范围
     * @param tableNamePattern 一个表名模式;必须匹配存储在数据库中的表名
     * @param types            表类型列表，它必须来自从getTableTypes返回的表类型列表，null返回所有类型
     * @return 每一行都是一个表描述
     */
    public List<TableInfo> getTables(String schemaPattern, String tableNamePattern, String[] types) {
        return getTables(getCatalog(), schemaPattern, tableNamePattern, types);
    }

    /**
     * <p>检索给定目录中可用表的描述。只返回与目录、模式、表名和类型条件匹配的表描述。</p>
     * <p>它们按TABLE_TYPE, TABLE_CAT, TABLE_SCHEMA和TABLE_NAME排序。</p>
     *
     * @param tableNamePattern 一个表名模式;必须匹配存储在数据库中的表名
     * @return 每一行都是一个表描述
     */
    public List<TableInfo> getTables(String tableNamePattern) {
        return getTables(getSchema(), tableNamePattern, getTableTypesToArray());
    }

    /**
     * <p>检索给定目录中可用表的描述。只返回与目录、模式、表名和类型条件匹配的表描述。</p>
     * <p>它们按TABLE_TYPE, TABLE_CAT, table_schema和TABLE_NAME排序。</p>
     *
     * @param tableNamePattern 一个表名模式;必须匹配存储在数据库中的表名
     * @param types            表类型列表，它必须来自从getTableTypes返回的表类型列表，null返回所有类型
     * @return 每一行都是一个表描述
     */
    public List<TableInfo> getTables(String tableNamePattern, String[] types) {
        return getTables(getSchema(), tableNamePattern, types);
    }

    /**
     * 检索此数据库中可用的表类型。结果按表类型排序。
     *
     * @return 每行有一个String，该列是一个表类型
     */
    public List<String> getTableTypes() {
        return HylfFunUtil.methodVoidReturnExec(() -> {
            if (HylfCollectionUtil.isNotEmpty(typeList)) {
                return typeList;
            }
            typeList = new ArrayList<>();
            ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(databaseMetaData::getTableTypes, "获取数据库表类型失败。");
            while (resultSet != null && resultSet.next()) {
                typeList.add(resultSet.getString(TABLE_TYPE));
            }
            HylfFunUtil.autoClose(resultSet);
            return typeList;
        });
    }

    /**
     * 检索此数据库中可用的表类型。结果按表类型排序。
     *
     * @param ignoreTypes 忽略的类型
     * @return 每行有一个String，该列是一个表类型
     */
    public String[] getTableTypesToArray(String... ignoreTypes) {
        if (HylfCollectionUtil.isEmpty(typeList)) {
            typeList = getTableTypes();
        }
        return HylfCollectionUtil.filterListToArray(typeList, String.class, ignoreTypes);
    }

    /**
     * <p>检索目录中每个可用表的访问权限描述。</p>
     * <p>注意，表特权适用于表中的一个或多个列。假设此特权适用于所有列是错误的(对于某些系统可能是正确的，但并非所有系统都是正确的)。</p>
     * <p>只返回与模式和表名条件匹配的特权。它们按TABLE_CAT、TABLE_SCHEMA、TABLE_NAME和PRIVILEGE排序。</p>
     *
     * @param catalog          目录名称;必须与存储在数据库中的目录名称匹配;“”检索那些没有目录的;null表示不应使用目录名称来缩小搜索范围
     * @param schemaPattern    模式名称模式;必须与存储在数据库中的模式名称匹配;""检索那些没有模式的;null表示不应该使用模式名称来缩小搜索范围
     * @param tableNamePattern 一个表名模式;必须匹配存储在数据库中的表名
     * @return 每一行都是一个表特权描述
     */
    public List<PrivilegeInfo> getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) {
        String logFormat = "获取数据库可用表的访问权限信息失败。[catalog = {}, schemaPattern = {}, tableNamePattern = {}]";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            String key = String.join("-", catalog, schemaPattern, tableNamePattern);
            tablePrivilegesInfoMap = HylfFunUtil.mapAddValue(tablePrivilegesInfoMap, key, () -> {
                ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(
                        () -> databaseMetaData.getTablePrivileges(catalog, schemaPattern, tableNamePattern)
                        , logFormat, catalog, schemaPattern, tableNamePattern);
                List<PrivilegeInfo> tablePrivilegesInfoList = HylfDataUtil.autoSetValue(resultSet, PrivilegeInfo.class);
                HylfFunUtil.autoClose(resultSet);
                return tablePrivilegesInfoList;
            });
            return tablePrivilegesInfoMap.get(key);
        });
    }

    /**
     * <p>检索目录中每个可用表的访问权限描述。</p>
     * <p>注意，表特权适用于表中的一个或多个列。假设此特权适用于所有列是错误的(对于某些系统可能是正确的，但并非所有系统都是正确的)。</p>
     * <p>只返回与模式和表名条件匹配的特权。它们按TABLE_CAT、TABLE_SCHEMA、TABLE_NAME和PRIVILEGE排序。</p>
     *
     * @param tableNamePattern 一个表名模式;必须匹配存储在数据库中的表名
     * @return 每一行都是一个表特权描述
     */
    public List<PrivilegeInfo> getTablePrivileges(String tableNamePattern) {
        return getTablePrivileges(getCatalog(), getSchema(), tableNamePattern);
    }

    /**
     * <p>检索在此数据库的特定模式中定义的表层次结构的描述。</p>
     * <p>只返回与目录、模式和表名匹配的表的超表信息。表名参数可以是一个完全限定名，在这种情况下，catalog和schemaPattern 参数将被忽略。</p>
     * <p>如果一个表没有超级表，它就不会在这里列出。超级表必须在与子表相同的目录和模式中定义。因此，类型描述不需要包含超表的这些信息。</p>
     * <p>如果驱动程序不支持类型层次结构，则返回空结果集。</p>
     *
     * @param catalog          目录名称;必须与存储在数据库中的目录名称匹配;“”检索那些没有目录的;null表示不应使用目录名称来缩小搜索范围
     * @param schemaPattern    模式名称模式;必须与存储在数据库中的模式名称匹配;""检索那些没有模式的;null表示不应该使用模式名称来缩小搜索范围
     * @param tableNamePattern 一个表名模式;必须匹配存储在数据库中的表名
     * @return 每一行都是一个类型描述
     */
    public List<SuperTablesInfo> getSuperTables(String catalog, String schemaPattern, String tableNamePattern) {
        String logFormat = "获取数据库SuperTables信息失败。[catalog = {}, schemaPattern = {}, tableNamePattern = {}]";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            String key = String.join("-", catalog, schemaPattern, tableNamePattern);
            superTableInfosMap = HylfFunUtil.mapAddValue(superTableInfosMap, key, () -> {
                ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(
                        () -> databaseMetaData.getSuperTables(catalog, schemaPattern, tableNamePattern)
                        , logFormat, catalog, schemaPattern, tableNamePattern);
                List<SuperTablesInfo> superTablesInfoList = HylfDataUtil.autoSetValue(resultSet, SuperTablesInfo.class);
                HylfFunUtil.autoClose(resultSet);
                return superTablesInfoList;
            });
            return superTableInfosMap.get(key);
        });
    }

    /**
     * <p>检索在此数据库的特定模式中定义的表层次结构的描述。</p>
     * <p>只返回与目录、模式和表名匹配的表的超表信息。表名参数可以是一个完全限定名，在这种情况下，catalog和schemaPattern 参数将被忽略。</p>
     * <p>如果一个表没有超级表，它就不会在这里列出。超级表必须在与子表相同的目录和模式中定义。因此，类型描述不需要包含超表的这些信息。</p>
     * <p>如果驱动程序不支持类型层次结构，则返回空结果集。</p>
     *
     * @param tableNamePattern 一个表名模式;必须匹配存储在数据库中的表名
     * @return 每一行都是一个类型描述
     */
    public List<SuperTablesInfo> getSuperTables(String tableNamePattern) {
        return getSuperTables(getCatalog(), getSchema(), tableNamePattern);
    }

    /**
     * 检索在更新行中的任何值时自动更新的表列的描述。它们是无序的。
     *
     * @param catalog 目录名称;必须与存储在数据库中的目录名称匹配;“”检索那些没有目录的;null表示不应使用目录名称来缩小搜索范围
     * @param schema  模式名;必须与存储在数据库中的模式名称匹配;""检索那些没有模式的;null表示不应该使用模式名称来缩小搜索范围
     * @param table   表名;必须匹配存储在数据库中的表名
     * @return 每一行是一个列的描述
     */
    public List<VersionColumnsInfo> getVersionColumns(String catalog, String schema, String table) {
        String logFormat = "获取数据库表VersionColumns信息失败。[catalog = {}, schema = {}, table = {}]";
        return HylfFunUtil.methodVoidReturnExec(() -> {
            String key = String.join("-", catalog, schema, table);
            versionColumnsInfoMap = HylfFunUtil.mapAddValue(versionColumnsInfoMap, key, () -> {
                ResultSet resultSet = HylfFunUtil.methodVoidReturnExec(
                        () -> databaseMetaData.getVersionColumns(catalog, schema, table)
                        , logFormat, catalog, schema, table);
                List<VersionColumnsInfo> versionColumnsInfoList = HylfDataUtil.autoSetValue(resultSet, VersionColumnsInfo.class);
                HylfFunUtil.autoClose(resultSet);
                return versionColumnsInfoList;
            });
            return versionColumnsInfoMap.get(key);
        });
    }

    /**
     * 检索在更新行中的任何值时自动更新的表列的描述。它们是无序的。
     *
     * @param table 表名;必须匹配存储在数据库中的表名
     * @return 每一行是一个列的描述
     */
    public List<VersionColumnsInfo> getVersionColumns(String table) {
        return getVersionColumns(getCatalog(), getSchema(), table);
    }
}
